#pragma once

#include <list>
#include <time.h>
#include <algorithm>
#include <assert.h>
#include "gnmutex.h"
#include <string>
#include <typeinfo>
#include <string.h>
using namespace std;

namespace KCP {
#ifndef Macro
#define Macro(T) #T
#endif

	// global flag for alloc and free flag.
#define  AllocFlag 0xA110C
#define  FreeFlag  0xDEA110C
#define  max_satisfy_count 3

//
// malloc just for c compiler, not only for c++
//
	template <typename T, typename Locker = CNonMutex>
	class CDynamicPoolEx {
		typedef T value_type;
		typedef Locker locker_type;

		// shell struct derived from value_type
		struct SShellT : value_type {
			int ref; // reference magic.
			SShellT() : ref(FreeFlag) {}

			template <typename ...Paras>
			SShellT(Paras&&... para) : ref(0), value_type(std::forward<Paras>(para)...) {}
			virtual ~SShellT() {}
		};

		// resource info.
		struct SResourceT {
			SShellT* array;
			int      size;
			SResourceT() :array(nullptr), size(0) {
			}
			SResourceT(SShellT* a, int s) :array(a), size(s) {
			}
		};
		typedef std::list<SShellT*> CObjectList;
		typedef std::list<SResourceT> CResourceList;

		struct Skiller {
			void operator() (SResourceT& Reource) {
				if (Reource.array) {
					::free(Reource.array);
					Reource.array = nullptr;
				}
			}
		};

	public:
		explicit CDynamicPoolEx() : m_init_size(1), m_grow_size(1), m_satisfy_count(0) {
			m_all.clear();
			m_free.clear();
		}

		virtual ~CDynamicPoolEx() {
			m_locker.Lock();
			m_free.clear();
			m_locker.Unlock();

			// release all resources.
			std::for_each(m_all.begin(), m_all.end(), Skiller());
		}

	public:
		// all alloc size
		virtual int GetAllAllocSize() const {
			return m_init_size + (m_all.size() - 1) * m_grow_size;
		}

		// left alloc size
		virtual int GetLeftAllocSize() const {
			return m_free.size();
		}

		// object name
		virtual const char* GetObjectName() const {
#if defined(_WIN32)
			static bool g_HasGet = false;
			static std::string strClassName;
			if (!g_HasGet) {
				const char* pszClassName = typeid(*this).name();
				const char* pszStart = strstr(pszClassName, "<");
				const char* pszNext = strstr(pszStart, ",");
				strClassName.assign(pszStart + 1, pszNext);
				g_HasGet = true;
			}
			return strClassName.c_str();
#else
			return typeid(*this).name();
#endif
		}

		// get used size
		virtual int GetUsedSize() const {
			return GetAllAllocSize() - GetLeftAllocSize();
		}

		virtual int GetInitSize() const {
			return m_init_size;
		}

		virtual int GetGrowSize() const {
			return m_grow_size;
		}

		virtual int GetAllocMemorySize() const {
			return GetAllAllocSize() * sizeof(T);
		}

	public:
		bool Init(int initSize, int growSize) {
			m_init_size = initSize;
			m_grow_size = growSize;
			if (!m_init_size && !m_grow_size) {
				return false;
			}

			if (m_init_size == 0) m_init_size = growSize;
			if (m_grow_size == 0) m_grow_size = initSize;
			if (_Allocate(m_init_size)) {
				return true;
			}
			return false;
		}

		// Get Object.
		template <typename... Args>
		value_type* FetchObj(Args&&... args) {
			SShellT* poShellT = _Fetch();
			if (!poShellT) {
				return nullptr;
			}

			// parameter constructor.
			new (poShellT) SShellT(std::forward<Args>(args)...);
			poShellT->ref = AllocFlag;
			return poShellT;
		}

		// release Object.
		void ReleaseObj(value_type* poT) {
			if (!poT) return;

			SShellT* poShellT = static_cast<SShellT*>(poT);
			if (!poShellT) return;
			assert(poShellT->ref == AllocFlag && "reference is not right flag");
			poShellT->ref = FreeFlag;

			// address check
			SResourceT t;
			if (!_address_check(poShellT, &t)) {
				return;
			}

			// call deallocate to c++ object.
			poShellT->~SShellT();

			// reallocate pShellT
			m_locker.Lock();
			m_free.push_back(poShellT);

			// only for window's debug.
#if defined(WIN32) && defined(_DEBUG)
			_memory_array_check(&t);
#endif
			m_locker.Unlock();
		}

		// gc check. can call outside as possible.
		void GCCheck() {
			for (auto it = m_all.begin(); it != m_all.end(); it++) {
				_memory_array_check(&*it);
			}
		}

	protected:
		void _memory_array_check(const SResourceT* t) {
			int all_size = 0; {// calculate all size.	
				for (auto it = m_all.begin(); it != m_all.end(); it++) {
					all_size += it->size;
				}
			}

			all_size -= m_init_size;
			all_size -= t->size;

			int real_size = int(m_free.size()) - int(t->size);
			if (all_size <= 0 || real_size <= 0) { // not enough now?
				return;
			}

			const int min_size = 10;
			float percent = float(real_size) / float(all_size);
			if (all_size > min_size && percent > 0.5f) { // over then 50, then release it.		
				if (m_satisfy_count++ >= max_satisfy_count) {
					_dealloc(t);
					m_satisfy_count = 0;
				}
			}
		}

		void _dealloc(const SResourceT* t) {
			if (!t) return;

			using CObjListIter = typename CObjectList::iterator;
			auto* it_array = new CObjListIter[t->size];

			// save should remove iterator.
			int index = 0;
			const SShellT* array = t->array;
			for (int i = 0; i < t->size; i++) {
				auto it = std::find(m_free.begin(), m_free.end(), &(array[i]));
				if (it == m_free.end()) {
					delete[] it_array;
					return;
				}
				else {
					it_array[index++] = it;
				}
			}

			// == now remove related resource. == 
			// remove from free list.
			for (int i = 0; i < index; i++) {
				m_free.erase(it_array[i]);
			}

			// remove from resource list.
			for (auto it = m_all.begin(); it != m_all.end(); it++) {
				if (it->array == t->array && it->size == t->size) {
					m_all.erase(it);
					break;
				}
			}
			::free(t->array);
			delete[] it_array;
		}

		// check poShellT address, and return it's array and array size.
		bool _address_check(SShellT* poShellT, SResourceT* t) const {
			auto it = m_all.begin();
			for (; it != m_all.end(); it++) {
				const SShellT* pArr = it->array;
				if (poShellT >= pArr && poShellT <= &pArr[it->size - 1]) {
					if (t) *t = (*it);
					break;
				}
			}
			return it != m_all.end();
		}

		// allocate size object.
		bool _Allocate(int nSize) {
			assert(nSize > 0 && "size <= 0");

			// new resource and push back
			SShellT* pstArray = (SShellT*)::malloc(sizeof(SShellT) * nSize);
			for (int i = 0; i < nSize; i++) {
				m_free.push_back(&pstArray[i]);
			}

			// push to all list objects
			m_all.emplace_back(pstArray, nSize);

			return true;
		}

		SShellT* _Fetch() {
			// lock it
			m_locker.Lock();

			if (m_free.empty()) { // realloc it.
				if (!_Allocate(m_grow_size)) {
					m_locker.Unlock();
					return nullptr;
				}
			}

			// get front resource.
			SShellT* poShellT = m_free.front();
			m_free.pop_front();

			// unlock
			m_locker.Unlock();

			return poShellT;
		}

	protected:
		CResourceList m_all;          // all resources
		CObjectList   m_free;         // allocated resource list
		locker_type   m_locker;       // allocated resource locker
		int           m_init_size;    // init size
		int           m_grow_size;    // grow size
		int           m_satisfy_count;// satisfy count.
	};
}